GitHubのOrganization配下のIssue・PRからアサインを外すスクリプトを書きました
サーモン大好き横山です。
とある事情で、「Organizationを抜ける前に、それぞれのリポジトリのアサインをOpen・Closeに関わらず抜けておいてね」という作業を依頼されました。
そこでまず、Issuesで archived:false is:closed assignee:tututen user:xxxxxx
を検索で検索して件数を調べることにしました
はい、1120件見つかりました。 一つのリポジトリだったらIssuesの画面開いて手で地道にアサインを外していくということもできたでしょうが、複数リポジトリまたいでこの数でしたので、GitHub APIを用いてアサインを外していくことにしました。
前提
GitHub APIを叩くためのPersonalTokenを取得します。
権限はRepo権限フルアクセスのものを作成します。
そのtokenは、任意の場所のファイルに保存しておきます。
$ vim ~/.github_token/github_unassignees_token $ chmod 0600 ~/.github_token/github_unassignees_token
また、pythonの requests
を使うので、利用する仮想環境にrequestsを入れてください
$ pip3 install requests
処理内容
Search issues and pull requests でアサインされてるIssue・PRを取得します。
取得した情報に、 Get an issue を呼び出せる url
があるので、その末尾に /assignees
を連結し、 Remove assignees from an issue を1件1件呼び出してアサインを外します
{ "id": 1, "node_id": "MDU6SXNzdWUx", "url": "https://api.github.com/repos/octocat/Hello-World/issues/1347", "repository_url": "https://api.github.com/repos/octocat/Hello-World", "labels_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/labels{/name}", "comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments", "events_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/events", "html_url": "https://github.com/octocat/Hello-World/issues/1347", : }
コード
書いたコードは以下になります。
import requests from requests.auth import HTTPBasicAuth from typing import List import os import json def get_assign_issue_and_pr_urls(session: requests.Session, username: str) -> List[str]: """ アサインしているIssue/PRのAPIURLのリストを返す(最大1000件) 参考: https://docs.github.com/en/rest/reference/search#search-issues-and-pull-requests """ per_page = 100 gh_org_user = "prismatix-jp" url_fn = ( lambda n: f"https://api.github.com/search/issues?q=archived:false+assignee:{username}+user:{gh_org_user}&per_page={per_page}&page={n}" ) resp = session.get(url_fn(1)) resp_data = json.loads(resp.text) if not resp_data["items"]: return [] unassignee_urls = [f'{item["url"]}/assignees' for item in resp_data["items"]] total_paging_count = resp_data["total_count"] // per_page + 1 # 合計1000件超えると怒られるので、1000件以上取得するときは1000件までに留める total_paging_count = min(1000 // per_page, total_paging_count) for i in range(2, total_paging_count + 1): resp = session.get(url_fn(i)) resp_data = json.loads(resp.text) if resp_data["items"]: unassignee_urls.extend( [f'{item["url"]}/assignees' for item in resp_data["items"]] ) return unassignee_urls def unassign_issue(session: requests.Session, api_url: str, username: str) -> None: """ 引数のAPIURLを元にusernameのアサインを外す 参考: https://docs.github.com/en/rest/reference/issues#remove-assignees-from-an-issue """ body_data = {"assignees": [username]} session.delete(api_url, data=json.dumps(body_data)) if __name__ == "__main__": # token with open(os.path.expanduser("~/.github_token/github_unassignees_token")) as f: gh_token = f.read().strip() target_username = "tututen" session = requests.Session() session.auth = HTTPBasicAuth(target_username, gh_token) session.headers["Accept"] = "application/vnd.github.v3+json" session.headers["Content-Type"] = "application/x-www-form-urlencoded" prev_urls = None while True: unassignee_urls = get_assign_issue_and_pr_urls(session, target_username) # 取得したurlsが0もしくは前回と同一の場合loopを抜ける if not unassignee_urls or prev_urls == unassignee_urls: break for url in unassignee_urls: unassign_issue(session, url, target_username) # 結果保持 prev_urls = unassignee_urls
結果
1120件 -> 23件まで減りました。
残った23件はなぜアサインが外せなかったといいますと、リポジトリが既に凍結(更新停止)のリポジトリでアサインが外せなかったIssue・PRでした。
GitHub APIをcurlで叩いても結果は404が返ってきます。
$ curl -i -H "Accept: application/vnd.github.v3+json" -u tututen:$(cat ~/.github_token/github_unassignees_token) https://api.github.com/repos/xxxxxxx/hoge-repositories/issues/33/assignees -XDELETE -d '{"assignees": ["tututen"]}' HTTP/2 404 server: GitHub.com date: Wed, xx Aug 2021 xx:00:47 GMT content-type: application/json; charset=utf-8 content-length: 132 x-oauth-scopes: repo x-accepted-oauth-scopes: github-authentication-token-expiration: 2021-xx-xx 07:01:01 UTC x-github-media-type: github.v3; format=json x-ratelimit-limit: 5000 x-ratelimit-remaining: 4988 x-ratelimit-reset: 162xxxx626 x-ratelimit-used: 12 x-ratelimit-resource: core access-control-expose-headers: ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset access-control-allow-origin: * strict-transport-security: max-age=31536000; includeSubdomains; preload x-frame-options: deny x-content-type-options: nosniff x-xss-protection: 0 referrer-policy: origin-when-cross-origin, strict-origin-when-cross-origin content-security-policy: default-src 'none' vary: Accept-Encoding, Accept, X-Requested-With x-github-request-id: ppp { "message": "Not Found", "documentation_url": "https://docs.github.com/rest/reference/issues#remove-assignees-from-an-issue" }
まとめ
GitHub API+PythonでOrganization配下に存在する1100件弱のIssue・PRのアサインをはずすことができました。
凍結したIssue・PRがWebからは見えるのに、APIから見ると 404 Not Found
が返ってくるのには少し腑に落ちてませんがそういうものなのでしょう。
この記事がだれかのお役に立てば幸いです。